/*
 * TcpSocket.cpp
 *
 *  Created on: 2009-8-20
 *      Author: fanliangliang
 */

#include "TcpSocket.h"
#include "TcpReader.h"
#include "TcpWritter.h"
#include "TimeoutTimer.h"
#include <xlogger.h>

CTcpSocket* CTcpSocket::NewL(MSocketListener& aListener) {
	CTcpSocket* self = NewLC(aListener);
	CleanupStack::Pop(self);
	return self;
}

CTcpSocket* CTcpSocket::NewLC(MSocketListener& aListener) {
	CTcpSocket* self = new (ELeave) CTcpSocket(aListener);
	CleanupStack::PushL(self);
	self->ConstuctL();
	return self;
}

CTcpSocket::CTcpSocket(MSocketListener& aListener)
:CActive(EPriorityHigh)
,iListener(aListener)
,iTcpReader(NULL)
,iTcpWriter(NULL)
,iTimer(NULL)
,iResolveTimeout(KDefaultTimeoutInSeconds)
,iConnectTimeout(KDefaultTimeoutInSeconds)
,iPreviousState(EUnconnectedState)
,iCurrentState(EUnconnectedState)
,iRemoteAddressCache(NULL)
,iLocalAddressCache(NULL) {
	CActiveScheduler::Add(this);
}

CTcpSocket::~CTcpSocket() {
	SynClose();
	iSocketServ.Close();
	delete iTcpReader;
	delete iTcpWriter;
	delete iTimer;
	delete iRemoteAddressCache;
	delete iLocalAddressCache;
}

void CTcpSocket::ConstuctL() {
	User::LeaveIfError(iSocketServ.Connect());
	iTcpReader = CTcpReader::NewL(*this, iSocket);
	iTcpWriter = CTcpWriter::NewL(*this, iSocket);
	iTimer = CTimeoutTimer::NewL(*this);
}

void CTcpSocket::ConnectL(const TDesC& aServerName, TUint aPort) {
	// Initiate connection process
	if (iCurrentState == EUnconnectedState) {
		iRemoteAddr.SetPort(aPort);
		if (iRemoteAddr.Input(aServerName) == KErrNone) {
			// server name is already a valid ip address
			ConnectL();
		} 
		else { // need to look up name using dns
			// Initiate DNS
			User::LeaveIfError(iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp));
			
			xtrace("Resolving hostname: {%S}", &aServerName);
			
			// Request time out
			iTimer->After(iResolveTimeout);
			
			// DNS request for name resolution
			iResolver.GetByName(aServerName, iNameEntry, iStatus);
			SetActive();
			
			SetState(EResolvingState);
		}
	}
}

void CTcpSocket::ConnectL(TUint32 aAddress, TUint aPort) {
	// Set up address information
	iRemoteAddr.SetAddress(aAddress);
	iRemoteAddr.SetPort(aPort);
	ConnectL();
}

void CTcpSocket::ConnectL() {
	// Initiate attempt to connect to a socket by IP address
	if (iCurrentState == EUnconnectedState || iCurrentState == EResolvingState) {
		// Open a TCP socket
		User::LeaveIfError(iSocket.Open(iSocketServ, KAfInet, KSockStream, KProtocolInetTcp));
		
		// Start a timeout
		iTimer->After(iConnectTimeout);
		
		// Initiate socket connection
		iSocket.Connect(iRemoteAddr, iStatus);
		SetActive();
		
		SetState(EConnectingState);
	}
}

void CTcpSocket::Close() {
	AsynClose();
}

void CTcpSocket::CancelAll() {
	Cancel();
	// Cancel all outstanding operations
	// Since we are connected, the only possibilities are read and write
	if (IsConnected()) {
		iTcpReader->Cancel();
		iTcpWriter->Cancel();
	}
}

void CTcpSocket::SynClose() {
	CancelAll();
	if (IsConnected()) {
		SetState(EClosingState);
		iSocket.Close();
		SetState(EUnconnectedState);
	}
}

void CTcpSocket::AsynClose() {
	CancelAll();
	if (IsConnected()) {
		iSocket.Shutdown(RSocket::ENormal, iStatus);
		SetActive();
		SetState(EClosingState);
	}
}

void CTcpSocket::InvalidateAddressCache() {
	if (iRemoteAddressCache != NULL) {
		delete iRemoteAddressCache;
		iRemoteAddressCache = NULL;
	}
	if (iLocalAddressCache != NULL) {
		delete iLocalAddressCache;
		iLocalAddressCache = NULL;
	}
}

TBool CTcpSocket::IsConnected() const {
	return iCurrentState == EConnectedState;
}

TSocketState CTcpSocket::State() const {
	return iCurrentState;
}

void CTcpSocket::Read() {
	if (IsConnected()) {
		iTcpReader->IssueRead();
	}
}

void CTcpSocket::Write(const TDesC8& aBuffer) {
	if (IsConnected()) {
		iTcpWriter->IssueWrite(aBuffer);
	}
}

const TInetAddr& CTcpSocket::RemoteName() const {
	return iRemoteAddr;
}

const TInetAddr& CTcpSocket::LocalName() const {
	return iLocalAddr;
}

const TDesC& CTcpSocket::RemoteAddress() const {
	if (iRemoteAddressCache == NULL) {
		iRemoteAddressCache = HBufC::NewL(KMaxIpv4AddressLength);
		TPtr ptr(iRemoteAddressCache->Des());
		iRemoteAddr.Output(ptr);
	}
	return *iRemoteAddressCache;
}

const TDesC& CTcpSocket::LocalAddress() const {
	if (iLocalAddressCache == NULL) {
		iLocalAddressCache = HBufC::NewL(KMaxIpv4AddressLength);
		TPtr ptr(iLocalAddressCache->Des());
		iLocalAddr.Output(ptr);
	}
	return *iLocalAddressCache;
}

TInt CTcpSocket::RemotePort() const {
	return iRemoteAddr.Port();
}

TInt CTcpSocket::LocalPort() const {
	return iLocalAddr.Port();
}

void CTcpSocket::SetTimeout(TTimeoutType aType, TTimeIntervalSeconds aTimeout) {
	if (aType & ETimeoutTypeRead) {
		iTcpReader->SetTimeout(aTimeout);
	}
	if (aType & ETimeoutTypeWrite) {
		iTcpWriter->SetTimeout(aTimeout);
	}
	if (aType & ETimeoutTypeResolve) {
		iResolveTimeout = aTimeout;
	}
	if (aType & ETimeoutTypeConnect) {
		iConnectTimeout = aTimeout;
	}
}

TTimeIntervalSeconds CTcpSocket::Timeout(TTimeoutType aType) const {
	if (aType & ETimeoutTypeRead) {
		return iTcpReader->Timeout();
	}
	if (aType & ETimeoutTypeWrite) {
		return iTcpWriter->Timeout();
	}
	if (aType & ETimeoutTypeResolve) {
		return iResolveTimeout;
	}
	if (aType & ETimeoutTypeConnect) {
		return iConnectTimeout;
	}
	return 0;
}

void CTcpSocket::DoCancel() {
	if (iCurrentState == EResolvingState || iCurrentState == EConnectingState) {
		iTimer->Cancel();
		
		// Cancel appropriate request to socket
		if (iCurrentState == EResolvingState) {
			// Cancel look up attempt
			iResolver.Cancel();
			iResolver.Close();
		}
		else {
			// Cancel connect attempt
			iSocket.CancelConnect();
			iSocket.Close();
		}
		
		SetState(EUnconnectedState);
	}
}

void CTcpSocket::RunL() {
	iTimer->Cancel();

	// iState flags what request was made, so its
	// completion can be handled appropriately
	switch (iCurrentState) {
	case EConnectingState:
		// IP connection request
		if (iStatus == KErrNone) {
			// Connection completed successfully
			iSocket.LocalName(iLocalAddr);
			SetState(EConnectedState);
			InvalidateAddressCache();
		}
		else {
			xtrace("Connection failed, socket will be closed, error code is {%d}", iStatus.Int());
			iSocket.Close();
			SetState(EUnconnectedState);
			iListener.OnSocketError(ECannotConnect);
		}
		break;
	case EResolvingState:
		iResolver.Close();
		if (iStatus == KErrNone) {
			// DNS look up successful
			const TNameRecord& nameRecord = iNameEntry();
			iRemoteAddr.SetAddress(TInetAddr::Cast(nameRecord.iAddr).Address());
			InvalidateAddressCache();
			// And connect to the IP address
			ConnectL();
		}
		else {
			xtrace("Host look up failed, error code is {%d}", iStatus.Int());
			// DNS lookup failed
			SetState(EUnconnectedState);
			iListener.OnSocketError(EHostNotFound);
		}
		break;
	case EClosingState:
		iSocket.Close();
		SetState(EUnconnectedState);
		break;
	default:
		break;
	};
}

void CTcpSocket::OnResolved() {
	iListener.OnResolved();
}

void CTcpSocket::OnConnected() {
	iListener.OnConnected();
}

void CTcpSocket::OnDisconnected() {
	iListener.OnDisconnected();
}

void CTcpSocket::OnReceived(const TDesC8& aBuffer) {
	iListener.OnReceived(aBuffer);
}

void CTcpSocket::OnStateChanged(TSocketState aState) {
	iListener.OnStateChanged(aState);
	if (iPreviousState == EResolvingState && aState == EConnectingState) {
		iListener.OnResolved();
	}
	else if (iPreviousState == EConnectingState && aState == EConnectedState) {
		iListener.OnConnected();
	}
	else if (iPreviousState == EClosingState && aState == EUnconnectedState) {
		iListener.OnDisconnected();
	}
}

void CTcpSocket::OnSocketError(TSocketError aError) {
	iListener.OnSocketError(aError);
	if (aError == ERemoteHostClosed) {
		AsynClose();
	}
}

void CTcpSocket::OnTimerExpired() {
	Cancel();
	if (iCurrentState == EResolvingState) {
		iListener.OnSocketError(EHostNotFound);
	}
	else if (iCurrentState == EConnectingState) {
		iListener.OnSocketError(ECannotConnect);
	}
}

void CTcpSocket::SetState(TSocketState aState) {
	if (iCurrentState != aState) {
		iPreviousState = iCurrentState;
		iCurrentState = aState;
		OnStateChanged(aState);
	}
}
